/*
 * Copyright (c) 2012 Johan Fylling
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy 
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights 
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
 * copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in 
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
 * THE SOFTWARE.
 */

part of engine;


typedef bool BeforeContact (DynamicBody body1, 
                            DynamicBody body2);


typedef bool BeforePenetration (DynamicBody body1, 
                                DynamicBody body2);


typedef void HandleCollision (DynamicBody body1,
                              DynamicBody body2,
                              ContactManifold manifold,
                              Vector2 penetration);


/**
 * Conveniency Wrapper of collision detection functionality.
 * Collision response must be implemented by registered [CollisionHandler].
 */
class CollisionManager {
  final BroadphaseDetector  broadphaseDetector;
  final NarrowphaseDetector narrowphaseDetector;
  final ContactDetector     contactDetector;
  int                       iterations     = 3;
  bool                      drawDebug      = false;
  int                       collisionCount = 0;
  
  CollisionManager (BroadphaseDetector this.broadphaseDetector,
                    NarrowphaseDetector this.narrowphaseDetector,
                    ContactDetector this.contactDetector);
  
  int get collisionPairCount => this.broadphaseDetector.collisionPairs.length;
  
  /** Detects collisions against [wo]; handing them over to the [CollisionHandler] for resolution. */
  void detectObjectCollisions (DynamicBody body, 
                               {BeforeContact beforeContact, 
                                BeforePenetration beforePenetration, 
                                HandleCollision handleCollision, 
                                List<DynamicBody> colliders}) {
    List<CollisionPair> collisionPairs = this.broadphaseDetector.detectBodyCollisions(body, beforeContact);
    
    for (CollisionPair pair in collisionPairs) {
      Vector2 penetration = new Vector2();
      if (this.narrowphaseDetector.detectCollision(pair.body1, 
                                    pair.body2,
                                    penetration)) {
        if (?colliders) {
          colliders.addLast(pair.body1.id == body.id ? pair.body2 : pair.body1);
        }
        
        // Draw debug info.
        DebugPainter debugPainter = (ServiceManager.getService("DebugPainter") as DebugPainter);
        if (this.drawDebug && debugPainter != null) {
          debugPainter.debugDrawLine(pair.body1.position, 
                                     pair.body1.position + penetration,
                                     style:"green");
        }
        pair.colliding = true;
        ++this.collisionCount;
        if (!?beforePenetration || beforePenetration(pair.body1, pair.body2)) {
          ContactManifold manifold = this.contactDetector.detectContact(pair.body1, 
                                                            pair.body2,
                                                            penetration);
          // Draw debug info.
          if (this.drawDebug && debugPainter != null) {
            for (ContactPoint point in manifold.points) {
              debugPainter.debugDrawLine(point.position, 
                  point.position + manifold.normal*point.magnitude);
              debugPainter.debugDrawPoint(point.position);
            }
          }
          if (?handleCollision) {
            handleCollision(pair.body1, pair.body2, manifold, penetration);
          }
        }
      } else {
        pair.colliding = false;
      }
    }
  }
  
  /** Detects any active collisions; handing them over to the [CollisionHandler] for resolution. */
  void detectCollisions ({BeforeContact beforeContact, 
                          BeforePenetration beforePenetration, 
                          HandleCollision handleCollision}) {
    this.collisionCount = 0;
    
    for (int i = 0; i < this.iterations; i++) {
      this.broadphaseDetector.detectCollisions(beforeContact);
      for (CollisionPair pair in this.broadphaseDetector.collisionPairs) {
        Vector2 penetration = new Vector2();
        if (this.narrowphaseDetector.detectCollision(pair.body1, 
                                      pair.body2,
                                      penetration)) {
          // Draw debug info.
          DebugPainter debugPainter = (ServiceManager.getService("DebugPainter") as DebugPainter);
          if (this.drawDebug && debugPainter != null) {
            debugPainter.debugDrawLine(pair.body1.position, 
                                       pair.body1.position + penetration,
                                       style:"green");
          }
          pair.colliding = true;
          ++this.collisionCount;
          if (!?beforePenetration || beforePenetration(pair.body1, pair.body2)) {
            ContactManifold manifold = this.contactDetector.detectContact(pair.body1, 
                                                              pair.body2,
                                                              penetration);
            // Draw debug info.
            if (this.drawDebug && debugPainter != null) {
              for (ContactPoint point in manifold.points) {
                debugPainter.debugDrawLine(point.position, 
                                           point.position + manifold.normal*point.magnitude);
                debugPainter.debugDrawPoint(point.position);
              }
            }
            if (?handleCollision) {
              handleCollision(pair.body1, pair.body2, manifold, penetration);
            }
          }
        } else {
          pair.colliding = false;
        }
      }
    }
  }
  
  DynamicBody raycast (Vector2 from, Vector2 to) {
    AABB aabb = new AABB();
    aabb.min.x = Math.min(from.x, to.x);
    aabb.min.y = Math.min(from.y, to.y);
    aabb.max.x = Math.max(from.x, to.x);
    aabb.max.y = Math.max(from.y, to.y);
    DynamicBody result = null;
    
    List<DynamicBody> collisions = this.broadphaseDetector.cull(aabb);
    
    for (DynamicBody body in collisions) {
      if (body.collidable != null &&
          body.collidable.raycast(from, to, body)) {
        result = body;
      }
    }
    return result;
  }
}
